<?php

require_once('php/lib/configured_object.php');

/**
 * Abstract database.
 *
 * @abstract
 * @author Pascal de Vink
 */
class Database extends ConfiguredObject {
	
	/**
	 * Last error or empty if no error occured.
	 *
	 * @var string
	 * @access public
	 */
	var $error;

	/**
	 * Timer to measure performance.
	 *
	 * @var Timer
	 * @access public
	 */
	var $timer;
	
	/**
	 * Constructs a new instance.
	 *
	 * Note: will automatically open database unless 'open=false' is defined 
	 *       in configuration or open parameter is false.
	 *
	 * @param array $config Database configuration
	 * @param boolean $open Indicates if database should be opened automatically (defaults to true)
	 * @access public
	 */
	function __construct($config = NULL, $open = true) {
		parent::__construct();

		# initialize configuration
		if ($config) {
			foreach ($config as $key => $value) {
				$this->config[$key] = $value;
			}
		}
		
		# initialize timer
		if ($this->config['timer'] && $GLOBALS[$this->config['timer']]) {
			$this->timer =& $GLOBALS[$this->config['timer']];
		}
		
		# open database
		if ($open && $this->config('auto_open', true)) {
			$this->open();
		}
	}

	/**
	 * Opens database.
	 *
	 * @return boolean True if database could be opened otherwise false
	 * @access public
	 */
	function open() {
		trigger_error(sprintf('%s() not implemented', 'open'), E_USER_ERROR);
	}
	
	/**
	 * Creates and opens database.
	 *
	 * @return boolean True if database could be created otherwise false
	 * @access public
	 */
	function create() {
		trigger_error(sprintf('%s() not implemented', 'create'), E_USER_ERROR);
	}
	
	/**
	 * Drops database.
	 *
	 * @return boolean True if database could be dropped otherwise false
	 * @access public
	 */
	function drop() {
		trigger_error(sprintf('%s() not implemented', 'drop'), E_USER_ERROR);
	}
	
	/**
	 * Closes database.
	 *
	 * @return boolean True if database was succesfully closed otherwise false
	 * @access public
	 */
	function close() {
		trigger_error(sprintf('%s() not implemented', 'close'), E_USER_ERROR);
	}

	/**
	 * Returns indicator if database is open
	 *
	 * @return boolean True is database is open otherwise false
	 * @access public
	 */
	function isOpen() {
		trigger_error(sprintf('%s() not implemented', 'isOpen'), E_USER_ERROR);
	}

	/**
	 * Performs given query.
	 *
	 * @param string $query SQL query to be executed
	 * @param int $limit Optional limit of results returned
	 * @param int $offset Optional offset from which to start results (starts at 0)
	 * @return ResultSet Result set or false if failed
	 * @access public
	 */
	function &query($query, $limit = NULL, $offset = NULL) {
		trigger_error(sprintf('%s() not implemented', 'query'), E_USER_ERROR);
	}

	/**
	 * Performs given query and returns first record.
	 *
	 * @param string $query SQL query to be executed
	 * @param int $limit Optional limit of results returned (if specified should be 1)
	 * @param int $offset Optional offset from which to start results (starts at 0)
	 * @return array Associative array containing record or false if failed
	 * @access public
	 */
	function &queryFirst($query, $offset = NULL) {
		trigger_error(sprintf('%s() not implemented', 'queryFirst'), E_USER_ERROR);
	}
	
	/**
	 * Performs given query and returns an array of all retrieved records.
	 *
	 * @param string $query SQL query to be executed
	 * @param int $limit Optional limit of results returned
	 * @param int $offset Optional offset from which to start results (starts at 0)
	 * @return array List of associative arrays containing the record fields or false if failed
	 * @access public
	 */
	function &queryAll($query, $limit = NULL, $offset = NULL) {
		trigger_error(sprintf('%s() not implemented', 'queryAll'), E_USER_ERROR);
	}

	/**
	 * Executes given command
	 *
	 * @param string $command SQL command to be executed
	 * @return int Number of affected rows or false if fails
	 * @access public
	 */
	function execute($command) {
		trigger_error(sprintf('%s() not implemented', 'execute'), E_USER_ERROR);
	}
	
	/**
	 * Performs insert of given fields in given table. The field values will be encoded.
	 *
	 * @param string $table_name Name of table
	 * @param array $fields Array of fields to be inserted (field_name => field_value)
	 * @return int Last generated unique ID or false if failed
	 * @access public
	 */
	function insert($table_name, $fields) {	
		$field_text = '';
		$value_text = '';
		$first = true;
		foreach ($fields as $name => $value) {
			if (!$first) {$field_text .= ', '; $value_text .= ', ';} else $first = false;
			$field_text .= $name;
			$value_text .= $this->encode($value);
		}
		
		$command = "INSERT INTO $table_name ($field_text) VALUES ($value_text)";
		//echo('INSERT:'.$command.'<br>')	;
		return $this->execute($command)? $this->lastInsertId() : false;
	}

	/**
	 * Performs update of given fields in given table. The field values will be encoded.
	 *
	 * @param string $table Name of table
	 * @param array $fields Array of fields to be inserted (field_name => field_value)
	 * @param string $where Optional where clause
	 * @return int Number of affected rows or false if failed
	 * @access public
	 */
	function update($table_name, $fields, $where = '') {		
		$update_text = '';
		$first = true;
		foreach ($fields as $name => $value) {
			if (!$first) {$update_text .= ', ';} else $first = false;
			$update_text .= $name . ' = ' . $this->encode($value);
		}

		$command = "UPDATE $table_name SET $update_text" . (($where != '')? " WHERE $where" : "");
		//printf($command);
		return $this->execute($command);
	}
	
	/**
	 * Performs delete in given table.
	 *
	 * @param string $table Name of table
	 * @param string $where Optional where clause
	 * @return int Number of affected rows or false if failed
	 * @access public
	 */
	function delete($table_name, $where = '') {
		$command = "DELETE FROM $table_name" . (($where != '')? " WHERE $where" : "");		
		return $this->execute($command);
	}
	
	/**
	 * Starts a new transaction.
	 *
	 * @return boolean True if transaction could be started otherwise false
	 * @access public
	 */
	function begin() {
		trigger_error(sprintf('%s() not implemented', 'begin'), E_USER_ERROR);
	}
	
	/**
	 * Rollbacks the active transaction.
	 *
	 * @return boolean True if rollback was succesfull otherwise false
	 */
	function rollback() {
		trigger_error(sprintf('%s() not implemented', 'rollback'), E_USER_ERROR);
	}
	 
	/**
	 * Commits the active transaction.
	 *
	 * @return boolean True if commit was succesfull otherwise false
	 * @access public
	 */
	function commit() {
		trigger_error(sprintf('%s() not implemented', 'commit'), E_USER_ERROR);
	}

	/**
	 * Returns list of all table names in database.
	 * 
	 * @return array Associative array of all tables ('name'=>tablename) or false if failed
	 * @access public
	 */
	function tables() {
		trigger_error(sprintf('%s() not implemented', 'tables'), E_USER_ERROR);
	}

	/**
	 * Returns list of all fields in given table.
	 * 
	 * @param string $table_name Name of table
	 * @return array Associative array of fields ('name' => fieldname, 'type' => fieldtype) or false if failed
	 * @access public
	 */
	function fields($table_name) {
		trigger_error(sprintf('%s() not implemented', 'fields'), E_USER_ERROR);
	}

	/**
	 * Returns generate unique ID of last INSERT statement.
	 *
	 * @return int Last generated insert ID or false if no such ID
	 */
	function lastInsertId() {
		trigger_error(sprintf('%s() not implemented', 'lastInsertId'), E_USER_ERROR);
	}

	/**
	 * Returns the number of rows affected by the last INSERT/UPDATE/DELETE statement.
	 *
	 * @return int Number of affected rows or false if failed
	 * @access public
	 */
	function lastAffected() {
		trigger_error(sprintf('%s() not implemented', 'lastAffected'), E_USER_ERROR);
	}

	/**
	 * Encodes given value for use in a SQL statement.
	 *
	 * @param string $value Value to be encoded
	 * @return string Encoded value
	 * @access public
	 */
	function encode($value) {
		trigger_error(sprintf('%s() not implemented', 'encode'), E_USER_ERROR);
	}
	
	/**
	 * Support routine that initializes settings for each operation.
	 *
	 * @param string $routine Routine name
	 * @param string $comment Additional info for routine call, e.g. parameters
	 * @param boolean $require_open Indicator if operation requires open database
	 * @access private
	 */
	function _begin($routine, $comment = '', $require_open = true) {
		
		// handle timer
		if ($this->timer) {
			$timer_tag = $this->config('timer_tag', 'db');
			$this->timer->start($timer_tag, $routine . '(' . $comment . ')');
		}
		
		// check if database is open
		if ($require_open && !$this->isOpen()) {
			return $this->_fail('No connection available for ' . $routine . '()');
		}
		
		return true;
	}

	/**
	 * Support routine that initialize settings for each operation.
	 *
	 * @param string $msg Message to be used for error
	 * @param unknown $return Value to be returned (defaults to false)
	 * @access private
	 */
	function _fail($msg = NULL, $return = false) {
		
		// handle error
		$this->error = $msg? $msg : $this->_error();
			
		// handle timer
		if ($this->timer) {
			$timer_tag = $this->config('timer_tag', 'db');
			$this->timer->stop($timer_tag, 'Failed: ' . $this->error);
		}
		
		// handle die-on-error
		if ($this->config['die_on_error']) {
			trigger_error($this->error, E_USER_ERROR);
		}

		return $return;
	}
	
	/**
	 * Support routine that initialize settings for each operation.
	 *
	 * @param unknown $return Value to be returned (defaults to true)
	 * @access private
	 */
	function _end($return = true) {

		// clear error
		$this->error = NULL;
		
		// handle timer
		if ($this->timer) {
			$timer_tag = $this->config('timer_tag', 'db');
			$this->timer->stop($timer_tag);
		}

		return $return;
	}
}

/**
 * Support class for database representing resultset.
 *
 * @access private
 * @abstract
 * @author Silvain
 */
class ResultSet extends Object {

	/**
	 * Database object.
	 *
	 * @var Database
	 * @access public
	 */
	var $database = NULL;

	/**
	 * Constructs new instance.
	 *
	 * @var Database $database Database for resultset
	 */
	function __construct(&$database) {
		$this->database = $database;
	}
		
	/**
	 * Fetches next row from resultset.
	 *
	 * @return array Associative array containing result or false when at end
	 * @access public
	 */
	function &next() {
		trigger_error(sprintf('%s() not implemented', 'next'), E_USER_ERROR);
	}

	/**
	 * Resets row pointer to start of resultset.
	 *
	 * @return nothing
	 * @access public
	 */
	function reset() {
		trigger_error(sprintf('%s() not implemented', 'reset'), E_USER_ERROR);
	}

	/**
	 * Returns number of records in result.
	 *
	 * @return int Number of records
	 * @access public
	 */
	function count() {
		trigger_error(sprintf('%s() not implemented', 'count'), E_USER_ERROR);
	}
	
	/**
	 * Returns indicator if resultset is empty.
	 *
	 * @return boolean True if resultset is empty otherwise false
	 * @access public
	 */
	function isEmpty() {
		trigger_error(sprintf('%s() not implemented', 'isEmpty'), E_USER_ERROR);
	}
	
	/**
	 * Returns list of fields with meta information about the fields in the resultset.
	 *
	 * @return array Associative array of fields in resultset ('name'=>name, 'type'=>type)
	 * @access public
	 */
	function fields() {
		trigger_error(sprintf('%s() not implemented', 'fields'), E_USER_ERROR);
	}

	/**
	 * Returns number of fields in result.
	 *
	 * @return int Number of fields.
	 * @access public
	 */
	function fieldCount() {
		trigger_error(sprintf('%s() not implemented', 'fieldCount'), E_USER_ERROR);
	}

	/**
	 * Dumps result to output. Will reposition afterwards to start of resultset (if supported).
	 *
	 * @access public
	 */
	function dump() {
		print('<table border=1>');
		
		# make sure we're at the beginning
		$rowcount = $this->count();
		if ($rowcount) $this->reset();

		# show header
		print('<tr>');
		$fields = $this->fields();
		foreach ($fields as $field) {
			print('<td align=center valign=top><b>&nbsp;' . $field['name'] . '&nbsp;</b></td>');
		}
		print('</tr>');
		
		# show records
		while (($row = $this->next())) {
			print('<tr>');
			foreach ($row as $field) {
				print('<td align=center valign=top>&nbsp;' . $field . '&nbsp;</td>');
			}
			print('</tr>');
		}
		
		print('</table>');
		
		# leave it in a clean state
		if ($rowcount) $this->reset();
	}
}
?>